/*
 *  $Id$
 *  Copyright [2010] [Panxiaobo] 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at
 *  
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package pxb.openid.rp.servlet;

import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pxb.openid.Association;
import pxb.openid.Constants;
import pxb.openid.OpenIdException;
import pxb.openid.ServletUtil;
import pxb.openid.rp.Associater;
import pxb.openid.rp.Auth;
import pxb.openid.rp.Discoverer;
import pxb.openid.rp.Verifier;
import pxb.openid.servlet.RedirectServlet;
import pxb.util.http.HttpConnector;

/**
 * 
 * @author Panxiaobo
 * 
 */
@SuppressWarnings("serial")
public abstract class BaseOpenidServlet extends RedirectServlet {

    private static final Logger log = LoggerFactory.getLogger(BaseOpenidServlet.class);
    protected Associater a = new Associater();
    protected Auth au = new Auth();
    protected Discoverer d = new Discoverer();
    protected Verifier f = new Verifier();

    public BaseOpenidServlet() {
        super();
        try {
            HttpConnector conn = (HttpConnector) Class.forName("pxb.util.http.impl.CommonsHttpClientImpl")
                    .newInstance();
            d.setHttpConnector(conn);
            log.info("using {} as HttpConnector", conn);
        } catch (Throwable e) {
            log.debug("Some thing error while loading pxb.util.http.impl.CommonsHttpClientImpl as HttpConnector,"
                    + " using default HttpConnector", e);
        }
        setFormRedirect(false);
        log.debug("use form redirect redirect: {}", isFormRedirect());
    }

    /**
     * 额外的请求数据
     * 
     * @return
     */
    protected Map<String, String> extraParams() {
        return null;
    }

    /**
     * 验证失败后执行
     * 
     * @param req
     * @param resp
     * @param values
     * @throws ServletException
     * @throws IOException
     */
    protected void fail(HttpServletRequest req, HttpServletResponse resp, Map<String, String> values)
            throws ServletException, IOException {
        resp.sendError(403, "Forbidden");
    }

    protected String getOpenidServerCache(String openidProvider) {
        return null;
    }

    protected void putOpenidServerCache(String openidProvider, String server) {
    }

    protected Association getAssociationCache(String handler) {
        return null;
    }

    protected void putAssociationCache(String handler, Association association) {
    }

    protected String mapOpenidProvider(String openidAlias) {
        return openidAlias;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest ,
     * javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String mode = req.getParameter(Constants.OPENID_MODE);
        if (mode == null) {
            String openidAlias = req.getParameter("openid");
            if (openidAlias == null || "".equals(openidAlias)) {
                throw new OpenIdException("OpenId Can't be Empty");
            }

            String openidProvider = mapOpenidProvider(openidAlias);
            String server = getOpenidServerCache(openidProvider);
            Association associate = getAssociationCache(openidProvider);
            if (associate == null || a.isExpired(associate)) {
                if (server == null) {
                    server = d.discover(openidProvider);
                }
                associate = a.associate(server);
                if (associate != null) {
                    putAssociationCache(associate.getId(), associate);
                    putOpenidServerCache(openidProvider, server);
                }
            }

            String ret = ServletUtil.getRequestUrl(req);
            redirect(req, resp, au.generate(associate, extraParams(), server, openidProvider, ret));
            return;
        } else if ("id_res".equals(mode)) {
            Map<String, String> values = ServletUtil.getRequestArgs(req);
            String handle = values.get(Constants.OPENID_ASSOCIATION_HANDLE);
            Association associate = getAssociationCache(handle);
            if (associate == null || a.isExpired(associate)) {
                this.fail(req, resp, values);
                return;
            }
            try {
                Map<String, String> trusted = f.verify(associate, values);
                String openid = (String) trusted.get(Constants.OPENID_IDENTITY);
                if (Constants.ID_SELECT.equals(openid)) {
                    openid = (String) trusted.get(Constants.OPENID_CLAIMED_ID);
                }
                this.success(req, resp, openid, trusted, values);
            } catch (Exception ex) {
                log.warn("openid verify failed", ex);
                this.fail(req, resp, values);
            }
            return;
        } else if ("error".equals(mode)) {
            Map<String, String> values = ServletUtil.getRequestArgs(req);
            this.fail(req, resp, values);
        } else if ("cancel".equals(mode)) {
            Map<String, String> values = ServletUtil.getRequestArgs(req);
            this.fail(req, resp, values);
        } else {
            throw new RuntimeException("Unknow openid.mode :" + mode);
        }
    }

    /**
     * 正确验证后执行
     * 
     * @param req
     * @param resp
     * @param openid
     * @param trusted
     *            OP发回的可以信赖的数据
     * @param values
     *            全部数据
     * @throws ServletException
     * @throws IOException
     */
    protected void success(HttpServletRequest req, HttpServletResponse resp, String openid,
            Map<String, String> trusted, Map<String, String> values) throws ServletException, IOException {
        resp.getWriter().println("openid" + openid + ",success");
    }

}
